/*
 * Copyright 2012 Alain Pannetrat (L1L1@gmx.com)
 * 
 * This file is part of cookie-miner.
 * 
 * cookie-miner is free software: you can redistribute it and/or modify 
 * it under the terms of the GNU General Public License as published by 
 * the Free Software Foundation, either version 3 of the License, or 
 * (at your option) any later version.
 * 
 * cookie-miner is distributed in the hope that it will be useful, but 
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License 
 * along with cookie-miner. If not, see http://www.gnu.org/licenses/.
 *
 * @version: $Id$
 *
 */
#include "url.h"

/*********************************************************
 *
 * MISC. HEX CONV.
 *
 */

static const char *HEX="0123456789ABCDEF";
static const std::string empty_string;

bool is_hex(const char c) 
{
	if (c>='0' && c<='9') return true;
	if (c>='a' && c<='f') return true;
	if (c>='A' && c<='F') return true;
	return false;
}

unsigned char hex_nibble(const char c) 
{
	if (c>='0' && c<='9') return c-'0';
	if (c>='a' && c<='f') return c-'a'+10;
	if (c>='A' && c<='F') return c-'A'+10;
	return 0;
}


/**********************************************************
 *
 * HTTPPACKET::URL
 *
 * scheme://username:password@domain:port/path?query_string#fragment_id
 *
 */

std::string URL::entity_encode(const std::string &entity, const std::string& exceptions)
{
	std::string res;

	for (unsigned u=0;u<entity.size();u++)
	{
		if ((entity[u]>='0' && entity[u]<='9') ||
		    (entity[u]>='a' && entity[u]<='z') ||
		    (entity[u]>='A' && entity[u]<='Z') ||
		    (entity[u]=='-') ||
		    (entity[u]=='_') ||
		    (entity[u]=='.') ||
		    (entity[u]=='~') ||
		    (exceptions.find(entity[u])!=std::string::npos))
		{
			res += entity[u];
		}
		else
		{
			res += '%';
			res += HEX[((unsigned char)entity[u])>>4];
			res += HEX[((unsigned char)entity[u])&0xF];
		}
	}
	return res;
}

std::string URL::entity_decode(const std::string &entity)
{
	std::string res;
	unsigned u = 0;

	while (u<entity.size())
	{
		if (u<entity.size()-2 && entity[u]=='%' && is_hex(entity[u+1]) && is_hex(entity[u+2]))
		{
			res += (char)((hex_nibble(entity[u+1])<<4)+hex_nibble(entity[u+2]));
			u+=3;
		}
		else if (entity[u]=='+')
		{
			res += ' ';
			u++;
		}
		else
		{
			res += entity[u];
			u++;
		}
	}
	return res;
}

URL::URL(const std::string& url)
{
	decode(url);
}

/* private */ bool URL::parse_query_string_item(const std::string& item)
{
	std::string name;
	std::string value;
	if (split_lr(name,value,"=",item))
		_query_string.push_back(KeyValue(entity_decode(name),entity_decode(value)));
	else
		_query_string.push_back(KeyValue(entity_decode(item),""));
	return true;
}

bool URL::decode(const std::string& url)
{
	std::string u(url);
	std::string qstring;
	KeyValue kv;

	split_lr(_scheme,u,"://",u);

	_username = "";
	_password = "";
	_domain = "";
	_port = "";
	_path = "";
	_fragment_id = "";

	if (!split_lr(_domain,u,"/",u))
	{
		_domain = u;
		u = "";
	}

	if (split_lr(_username,_domain,"@",_domain))
	{
		split_lr(_username,_password,":",_username);
		_username = entity_decode(_username);
		_password = entity_decode(_password);
	}

	split_lr(_domain,_port,":",_domain);

	split_lr(u,_fragment_id,"#",u);
	_fragment_id=entity_decode(_fragment_id);

	if (split_lr(_path,qstring,"?",u))
	{
		std::string candidate;

		_path = "/" + entity_decode(_path);
		while (split_lr(candidate,qstring,"&",qstring)) parse_query_string_item(candidate);
		while (split_lr(candidate,qstring,";",qstring)) parse_query_string_item(candidate);
		if (qstring.size()) parse_query_string_item(qstring); // tailing item
	}
	else
		_path = "/" + entity_decode(u);

	return true;
}

std::string URL::encode_relative() const
{
	std::string ret(entity_encode(_path,"/"));

	if (has_query_string()) ret += "?" + encode_query_string();
	if (has_fragment_id()) ret += "#" + entity_encode(_fragment_id);
	return ret;
}

std::string URL::encode() const
{
	std::string ret(_scheme + "://");

	if (has_username()) 
	{
		ret+= entity_encode(_username);
	        if (has_password()) ret += ":" + entity_encode(_password);
		ret += "@"; 
	}
	ret += _domain;
	if (has_port()) ret += ":" + _port;
	ret += encode_relative();
	
	return ret;
}

std::string URL::encode_query_string() const
{
	std::string retval;
	KeyValueList::const_iterator it=_query_string.begin();
	

	if (it==_query_string.end())
		return retval;

	retval = entity_encode(it->first);
	if (it->second.size())	retval += "=" + entity_encode(it->second);

	++it;

	while (it!=_query_string.end())
	{
		retval += "&" + entity_encode(it->first);
		if (it->second.size()) retval += "=" + entity_encode(it->second);
		++it;
	}
	return retval;
}
		
const std::string& URL::query_string_value(const std::string& key) const
{
	static std::string dummy;

	for (KeyValueList::const_iterator it=_query_string.begin();
	     it!=_query_string.end();
	     ++it)
	{
		if (key==it->first) return it->second;
	}
	return dummy;
}

bool URL::query_string_value_defined(const std::string& key) const
{
	static std::string dummy;

	for (KeyValueList::const_iterator it=_query_string.begin();
	     it!=_query_string.end();
	     ++it)
	{
		if (key==it->first) return true;
	}
	return false;
}

/************************************
#include <iostream>
#include <cassert>

using std::cout;

int main()
{
	std::string original ="http://Jojo:lApIn@www.example.com:8888/chemin/d/acc%C3%A8s.php?q=req&id=req2#signet"; 
	URL url;
	
	assert(url.decode(original));

	cout << "scheme: [" 	<< url.scheme() << "]\n";
	cout << "username: ["	<< url.username() << "]\n";
	cout << "password: ["	<< url.password() << "]\n";
	cout << "domain: ["	<< url.domain() << "]\n";
	cout << "port: [" 	<< url.port() << "]\n";
	cout << "path: ["	<< url.path() << "]\n";
	cout << "qs: ["		<< url.encode_query_string() << "]\n";
	cout << "qs[id]: ["	<< url.query_string_value("id") << "]\n";
	cout << "fragment: [" 	<< url.fragment_id() << "]\n";
	cout << "rel_url: ["	<< url.encode_relative() << "]\n";
	cout << "enc_url: ["	<< url.encode() << "]\n";
	cout << "org_url: ["	<< original << "]\n";
	cout << "done.\n";
}

*/
